/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation;
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

//
// Network topology
//
//  h0                                 h0-n0, h1-n0, n1-h2, n2-h2: 2Mb/s, 10ms
//     \    ---- n1 -----              n0-n1, n0-n2:               2Mb/s, 50ms
//      \  /             |
//       n0              n3 --- h2
//      /  \             |
//     /    ---- n2 ----
//  h1
//
// - all links are point-to-point links with indicated one-way BW/delay
// - FTP/TCP flow from n0 to n3, starting at time 1.2 to time 1.35 sec.
// - DropTail queues 
// - Tracing of queues and packet receptions to file "simple-global-routing.tr"

#include <iostream>
#include <fstream>
#include <string>
#include <cassert>

#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>

#include "ns3/core-module.h"
#include "ns3/simulator-module.h"
#include "ns3/node-module.h"
#include "ns3/helper-module.h"
#include "ns3/config-store.h"
#include "ns3/flow-client-application.h"



using namespace ns3;
using namespace texcp_routing;


NS_LOG_COMPONENT_DEFINE ("SimpleGlobalRoutingExample");

void
LogTime ()
{
  NS_LOG_INFO ("Time is " << Simulator::Now());
  Simulator::Schedule(Seconds(5), MakeEvent( &LogTime));
}

typedef std::vector<NetDeviceContainer> NetDevices;
typedef std::vector<NetDeviceContainer>::iterator NetDevicesIterator;

void assign_network( Ipv4AddressHelper *ipv4, NetDevices *netdevices )
{
  NetDevicesIterator iter;
  for (iter=netdevices->begin(); iter != netdevices->end(); iter++) {
    ipv4->Assign (*iter);
    ipv4->NewNetwork();
  }                               
}

void printIPs(NodeContainer nodes)
{
  Ipv4Address loopback("127.0.0.1");

  NodeContainer::Iterator iter;

  for (iter = nodes.Begin(); iter != nodes.End(); iter++) {
    Ptr<Node> node = (*iter);
    Ptr<Ipv4> ipv4 = node->GetObject<Ipv4>();
    NS_ASSERT(ipv4 != 0);
                       
    for (uint32_t index = 0; index < ipv4->GetNInterfaces(); index++) {
      Ipv4InterfaceAddress ipv4Address = ipv4->GetAddress(index,0);

      if (ipv4Address.GetLocal() != loopback) {
        std::cout << " node " << node->GetId()<<":"<< index << " ip " << ipv4Address.GetLocal() << " mask " << ipv4Address.GetMask() << std::endl;
      }
      // end iterating over interfaces
    }
  // end iterate over nodes
  }     
}

void FlowUpdate ( Ptr<Node> client, int flowNum )
{

  for (uint32_t appCount = 0; appCount < client->GetNApplications () ; appCount ++ )
  {
    Ptr<Application> app = client->GetApplication(appCount);
    if (app )
    {
      Ptr<FlowClientApplication> flowClientApplication = DynamicCast<FlowClientApplication> (app);
      if (flowClientApplication)
      {
        NS_LOG_INFO ("App found.");
        flowClientApplication->SetAttribute("FlowMax", UintegerValue(flowNum));
      }
    }

  }
  NS_LOG_INFO ("CHECK");
}

double
FlowSplitUpdate(std::vector<Ptr<Ipv4ReactTableEntry> > *availableRoutes, double sumBytes, double sumLostBytes)
{
  //NS_ASSERT( sumLoss <0 );
  int numPaths = availableRoutes->size();
  Ptr<Ipv4ReactTableEntry> route;
  std::vector<Ptr<Ipv4ReactTableEntry> >::iterator routeIter;

  //if it was in the default table, must exist
  NS_ASSERT(numPaths);
  //not much choice here...
  if(numPaths == 1)
  {
    route = availableRoutes->front();
    route->SetFlowSplit(1);
    return (1.0);
  }

  double flowSplit;

  //aim for equal utilization in absence of loss
  if (!sumLostBytes) {
    flowSplit = 1.0/numPaths;
    for( routeIter = availableRoutes->begin(); routeIter != availableRoutes->end(); routeIter++ )
    {
      route = (*routeIter);
      route->SetFlowSplit(flowSplit);
    }
    return 1;
  }


  std::cout << "totalBytes: " << sumBytes << "\ttotalLoss: " << sumLostBytes << "\tloss " << double(sumLostBytes)/sumBytes << "\tgoodput: " << sumBytes-sumLostBytes<<std::endl;

  //double alpha = 0.5;

  //double bw_pool = 0.0;
  //double flowSplitMin = 0.05;
  double coefficient;
  double sum=0.0;

  for( routeIter = availableRoutes->begin(); routeIter != availableRoutes->end(); routeIter++ ) 
  {
    route = (*routeIter);
    coefficient = route->GetInstTxmit();
    coefficient *= coefficient;

    double loss = route->GetInstRexmit();
    if (!loss)
      loss = 1;

    coefficient /= loss;

    sum += coefficient;
  }

  std::cout << "Sum is " << sum << std::endl;

  double lossdriven;
  double conservative;
  double equalisation;

  for( routeIter = availableRoutes->begin(); routeIter != availableRoutes->end(); routeIter++ ) 
  {
    route = (*routeIter);

    double loss = route->GetInstRexmit();
    if (!loss)
      loss = 1;

    lossdriven = route->GetInstTxmit();
    lossdriven *= route->GetInstTxmit();
    lossdriven /= loss;
    lossdriven /= sum;

    conservative = route->GetInstTxmit() / sumBytes ;

    equalisation = 1.0/numPaths;

    flowSplit = 1.0*lossdriven + 0.0*conservative + 0.0*equalisation;


    loss /= route->GetInstTxmit();

    route->SetFlowSplit(flowSplit);


    std::cout << "lossdriven " << lossdriven << "\tconservative " << conservative << "\tequalisation " << equalisation<< "\tloss " <<  loss <<"\tnew " << flowSplit << std::endl;
  }

  return 1;
}



int
main (int argc, char *argv[])
{
   CommandLine cmd;
   cmd.Parse (argc,argv);
   
   ConfigStore config;
   config.ConfigureDefaults ();
   Config::SetDefault("ns3::TcpSocket::SegmentSize", UintegerValue(1460));
       

  // Users may find it convenient to turn on explicit debugging
  // for selected modules; the below lines suggest how to do this
#if 1
  LogComponentEnable ("SimpleGlobalRoutingExample", LOG_LEVEL_INFO);
  //LogComponentEnable("Ipv4ReactRouting", LOG_LEVEL_ALL);
  //LogComponentEnable("Ipv4ReactRoutingHelper", LOG_LEVEL_ALL);
  //LogComponentEnable("Ipv4ReactTableEntry", LOG_LEVEL_ALL);
  //LogComponentEnable("FlowServerApplication", LOG_LEVEL_ALL);
  //LogComponentEnable("FlowClientApplication", LOG_LEVEL_ALL);
  //LogComponentEnable("TcpSocketImpl", LOG_LEVEL_ALL);
  //LogComponentEnable("Ipv4L3Protocol", LOG_LEVEL_ALL);
#endif
  Time endtime = Seconds(1200);
                                                       
  NS_LOG_INFO ("Create nodes.");
  NodeContainer sources;
  NodeContainer destinations;
  sources.Create (2);
  destinations.Create (2);
  NodeContainer core;
  core.Create (6);
  NodeContainer ingress;
  ingress.Create (2);
  NodeContainer egress;
  egress.Create (2);
 
  //hosts to ingress nodes
  NodeContainer s0i0 = NodeContainer (sources.Get(0), ingress.Get (0));
  NodeContainer s1i1 = NodeContainer (sources.Get(1), ingress.Get (1));
  //NodeContainer s2i2 = NodeContainer (sources.Get(2), ingress.Get (2));

  //ingress nodes to core nodes
  NodeContainer i0c0 = NodeContainer (ingress.Get(0), core.Get (0));
  NodeContainer i0c1 = NodeContainer (ingress.Get(0), core.Get (1));
  NodeContainer i0c2 = NodeContainer (ingress.Get(0), core.Get (2));
  NodeContainer i1c0 = NodeContainer (ingress.Get(1), core.Get (0));
  NodeContainer i1c1 = NodeContainer (ingress.Get(1), core.Get (1));
  NodeContainer i1c2 = NodeContainer (ingress.Get(1), core.Get (2));
  //NodeContainer i2c0 = NodeContainer (ingress.Get(2), core.Get (0));
  //NodeContainer i2c1 = NodeContainer (ingress.Get(2), core.Get (1));
  //NodeContainer i2c2 = NodeContainer (ingress.Get(2), core.Get (2));
     
  //internal core connections
  NodeContainer c0c3 = NodeContainer (core.Get(0), core.Get (3));
  NodeContainer c1c4 = NodeContainer (core.Get(1), core.Get (4));
  NodeContainer c2c5 = NodeContainer (core.Get(2), core.Get (5));

  //core to egress
  NodeContainer c3e0 = NodeContainer (core.Get(3), egress.Get (0));
  NodeContainer c3e1 = NodeContainer (core.Get(3), egress.Get (1));
  //NodeContainer c3e2 = NodeContainer (core.Get(3), egress.Get (2));
  NodeContainer c4e0 = NodeContainer (core.Get(4), egress.Get (0));
  NodeContainer c4e1 = NodeContainer (core.Get(4), egress.Get (1));
  //NodeContainer c4e2 = NodeContainer (core.Get(4), egress.Get (2));
  NodeContainer c5e0 = NodeContainer (core.Get(5), egress.Get (0));
  NodeContainer c5e1 = NodeContainer (core.Get(5), egress.Get (1));
  //NodeContainer c5e2 = NodeContainer (core.Get(5), egress.Get (2));

  //egress to hosts
  NodeContainer e0d0 = NodeContainer (egress.Get (0), destinations.Get(0));
  NodeContainer e1d1 = NodeContainer (egress.Get (1), destinations.Get(1));
  //NodeContainer e2d2 = NodeContainer (egress.Get (2), destinations.Get(2));

  //install react routing in ingress nodes
  InternetStackHelper reactStack;
  Ipv4ReactRoutingHelper reactRoutingHelper;
  reactRoutingHelper.SetAttribute("FlowSplitUpdateFunc", CallbackValue(MakeCallback(&FlowSplitUpdate)));
  reactRoutingHelper.SetAttribute("UpdatePrefInterval", TimeValue(Seconds(4)));
  reactRoutingHelper.SetAttribute("UpdateLexInterval", TimeValue(Seconds(2)));
  
  Ipv4GlobalRoutingHelper ipv4GlobalRoutingHelper;
  Ipv4ListRoutingHelper listRouting;
  listRouting.Add (reactRoutingHelper, 1);
  listRouting.Add (ipv4GlobalRoutingHelper, -1);
  reactStack.SetRoutingHelper (listRouting);    
  reactStack.Install (ingress);

  //install default stack in core and egress
  InternetStackHelper internet;
  internet.Install (core);
  internet.Install (egress);

  //install NSC stack in hosts
  NodeContainer reflex_hosts = NodeContainer( sources, destinations);
  internet.SetTcp ("ns3::NscTcpL4Protocol","Library",StringValue("liblinux2.6.26.so"));
  internet.Install (reflex_hosts);

  //activate reflex on hosts.
  NodeContainer::Iterator node;
  for ( node = reflex_hosts.Begin(); node != reflex_hosts.End(); node++)
  {
      std::ostringstream tcp_ecn;

      tcp_ecn << "/NodeList/" << (*node)->GetId() << "/$ns3::Ns3NscStack<linux2.6.26>/net.ipv4.tcp_ecn";
      Config::Set (tcp_ecn.str(), StringValue("3"));
  }
 


  NS_LOG_INFO ("Create channels.");
  PointToPointHelper p2p;

  NS_LOG_INFO ("\thosts to ingress.");
  p2p.SetDeviceAttribute ("DataRate", StringValue ("100Mbps"));
  p2p.SetChannelAttribute ("Delay", StringValue ("15ms"));
  NetDevices nd_s_i;
  nd_s_i.push_back (p2p.Install (s0i0));
  nd_s_i.push_back (p2p.Install (s1i1));
  //nd_s_i.push_back (p2p.Install (s2i2));

  //egress to host
  NS_LOG_INFO ("\tegress to host.");
  p2p.SetDeviceAttribute ("DataRate", StringValue ("100Mbps"));
  p2p.SetChannelAttribute ("Delay", StringValue ("15ms"));
  NetDevices nd_e_d;
  nd_e_d.push_back (p2p.Install (e0d0));
  nd_e_d.push_back (p2p.Install (e1d1));
  //nd_e_d.push_back (p2p.Install (e2d2));
 
  NS_LOG_INFO ("\tingress to core.");

  std::string dataRate = "100Mbps";
  p2p.SetDeviceAttribute ("DataRate", StringValue (dataRate));
  p2p.SetChannelAttribute ("Delay", StringValue ("15ms"));
  p2p.SetQueue("ns3::texcp_routing::TexcpQueue", "DataRate", StringValue(dataRate));
  //p2p.SetDeviceAttribute ("DataRate", StringValue ("200Mbps"));
  NetDevices nd_i_c;
  nd_i_c.push_back (p2p.Install (i0c0));
  nd_i_c.push_back (p2p.Install (i0c1));
  nd_i_c.push_back (p2p.Install (i0c2));
  nd_i_c.push_back (p2p.Install (i1c0));
  nd_i_c.push_back (p2p.Install (i1c1));
  nd_i_c.push_back (p2p.Install (i1c2));
  //nd_i_c.push_back (p2p.Install (i2c0));
 // nd_i_c.push_back (p2p.Install (i2c1));
  //nd_i_c.push_back (p2p.Install (i2c2));

  NS_LOG_INFO ("\tcore to egress.");
  NetDevices nd_c_e;
  nd_c_e.push_back (p2p.Install (c3e0));
  nd_c_e.push_back (p2p.Install (c4e0));
  nd_c_e.push_back (p2p.Install (c5e0));
  nd_c_e.push_back (p2p.Install (c3e1));
  nd_c_e.push_back (p2p.Install (c4e1));
  nd_c_e.push_back (p2p.Install (c5e1));
  //nd_c_e.push_back (p2p.Install (c3e2));
 // nd_c_e.push_back (p2p.Install (c4e2));
 // nd_c_e.push_back (p2p.Install (c5e2));

  NS_LOG_INFO ("\tcore to core.");
  NetDevices nd_c_c;

  // Configure Link 1
  dataRate = "50Mbps";
  p2p.SetDeviceAttribute ("DataRate", StringValue (dataRate));
  p2p.SetQueue("ns3::texcp_routing::TexcpQueue", "DataRate", StringValue(dataRate));
  //p2p.SetDeviceAttribute ("DataRate", StringValue ("15Mbps"));
  nd_c_c.push_back (p2p.Install (c0c3));

  // Configure Link 2
  dataRate = "30Mbps";
  p2p.SetDeviceAttribute ("DataRate", StringValue (dataRate));
  p2p.SetQueue("ns3::texcp_routing::TexcpQueue", "DataRate", StringValue(dataRate));
  //p2p.SetDeviceAttribute ("DataRate", StringValue ("15Mbps"));
  nd_c_c.push_back (p2p.Install (c1c4));

  // Configure Link 3
  dataRate = "40Mbps";
  p2p.SetDeviceAttribute ("DataRate", StringValue (dataRate));
  p2p.SetQueue("ns3::texcp_routing::TexcpQueue", "DataRate", StringValue(dataRate));
  //p2p.SetDeviceAttribute ("DataRate", StringValue ("15Mbps"));
  nd_c_c.push_back (p2p.Install (c2c5));
                     
  
  // Later, we add IP addresses.  
  NS_LOG_INFO ("Assign IP Addresses.");
  Ipv4AddressHelper ipv4;
  Ipv4InterfaceContainer interfaces;

  NetDevicesIterator iter;

  ipv4.SetBase ("172.0.0.0", "255.255.255.0");
  assign_network (&ipv4, &nd_s_i);

  ipv4.SetBase ("10.0.0.0", "255.255.255.0");
  assign_network (&ipv4, &nd_i_c);
  assign_network (&ipv4, &nd_c_c);
  assign_network (&ipv4, &nd_c_e);
  ipv4.SetBase ("192.0.0.0", "255.255.255.0");
  assign_network (&ipv4, &nd_e_d);

  // Create router nodes, initialize routing database and set up the routing
  // tables in the nodes.
  ipv4GlobalRoutingHelper.PopulateRoutingTables ();
 
  config.ConfigureAttributes ();
  

  // install routing table for each ingress
  for (NodeContainer::Iterator iter = ingress.Begin(); iter != ingress.End(); iter++) {

    Ptr<Ipv4> ipv4 = (*iter)->GetObject<Ipv4>();
    NS_ASSERT(ipv4 != 0);
    //check for right topology
    NS_ASSERT(ipv4->GetNInterfaces()==5);

    Ptr<Ipv4ReactRouting> react_routing = reactRoutingHelper.GetReactRouting (ipv4);

    printIPs(destinations);

    for (NodeContainer::Iterator dst = destinations.Begin(); dst != destinations.End(); dst++) {
      Ptr<Ipv4> dst_ipv4 = (*dst)->GetObject<Ipv4>();
      NS_ASSERT(dst_ipv4 != 0);
      //only support single address for now, interface 1
      NS_ASSERT(dst_ipv4->GetNInterfaces() == 2);
      Ipv4InterfaceAddress ipv4Address = dst_ipv4->GetAddress(1,0);
      Ipv4Address network = ipv4Address.GetLocal().CombineMask(ipv4Address.GetMask());

      react_routing->AddDefaultNetworkRouteTo (network, ipv4Address.GetMask(), 2);
      react_routing->AddNetworkRouteTo (network, ipv4Address.GetMask(), 3);
      react_routing->AddNetworkRouteTo (network, ipv4Address.GetMask(), 4);
    }
  }

  NS_LOG_INFO ("Create Applications.");
  uint16_t port = 8080;
  Time starttime = Seconds(1.0);

  std::vector<ApplicationContainer> flowClients;

  NS_LOG_INFO ("Install servers.");
  FlowServerHelper flowServer ( Address (InetSocketAddress (Ipv4Address::GetAny (), port)));
  ApplicationContainer appServer = flowServer.Install (sources);
  appServer.Start (starttime);
  appServer.Stop (endtime);
  

  for (NodeContainer::Iterator iter = sources.Begin(); iter != sources.End(); iter++) {
    Ptr<Ipv4> server_ipv4 = (*iter)->GetObject<Ipv4>();
    Ipv4InterfaceAddress ipv4Address = server_ipv4->GetAddress(1,0);

    // install client in each destination
    FlowClientHelper flowClient (Address (InetSocketAddress (ipv4Address.GetLocal(), port)));
    flowClient.SetAttribute ("FlowMax", UintegerValue (40));
    flowClient.SetAttribute ("FlowSize", RandomVariableValue (WeibullVariable (0.5*1000*1000, 0.5)));


    ApplicationContainer appClient = flowClient.Install (destinations);
    appClient.Start (starttime);
    appClient.Stop (endtime);
  }


  NS_LOG_INFO ("Run Simulation.");
  LogTime();
  //Ptr<Node> balancer = ingress.Get (0);
  //Ptr<Ipv4> ipv4_balancer = balancer->GetObject<Ipv4> ();
  Simulator::Schedule (Seconds (300),&FlowUpdate, destinations.Get(0),100);
  Simulator::Schedule (Seconds (300),&FlowUpdate, destinations.Get(1),20);
  //Simulator::Schedule (Seconds (500),&FlowUpdate, destinations.Get(2),20);

  Simulator::Schedule (Seconds (750),&FlowUpdate, destinations.Get(0),20);
  Simulator::Schedule (Seconds (750),&FlowUpdate, destinations.Get(1),100);
  //Simulator::Schedule (Seconds (1000),&FlowUpdate, destinations.Get(2),60);

  /*Simulator::Schedule (Seconds (900),&FlowUpdate, destinations.Get(0),40);
  Simulator::Schedule (Seconds (900),&FlowUpdate, destinations.Get(1),40);
  //Simulator::Schedule (Seconds (1500),&FlowUpdate, destinations.Get(2),40);

  Simulator::Schedule (Seconds (1200),&FlowUpdate, destinations.Get(0),60);
  Simulator::Schedule (Seconds (1200),&FlowUpdate, destinations.Get(1),60);
  //Simulator::Schedule (Seconds (1500),&FlowUpdate, destinations.Get(2),40);*/


  //routeInstance->AddNetworkRouteTo ("192.168.0.0", "255.255.255.0", 3);

  reactRoutingHelper.EnableAscii("reflex",ingress);
  //reactRoutingHelper.TraceQueue("reflex", core);
  NodeContainer bottleNeck = NodeContainer(core.Get(0), core.Get(1), core.Get(2));
  Ipv4TexcpRoutingHelper texcpHelper;
  texcpHelper.TraceQueue("queue", bottleNeck);

  FlowClientHelper flowClient;
  flowClient.EnableAscii ("flowclient", destinations);

  Simulator::Stop (endtime);
  Simulator::Run ();
  NS_LOG_INFO ("Done.");

  Simulator::Destroy ();
  return 0;
}
